﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace CNative.WebApi.Utils
{
    public class AssemblyHelper
    {
        private static List<Assembly> _referenceAssembly = new List<Assembly>();

        public static List<Assembly> GetAssemblies(params string[] virtualPaths)
        {
            var referenceAssemblies = new List<Assembly>();
            if (virtualPaths.Any())
            {
                referenceAssemblies = GetReferenceAssembly(virtualPaths);
            }
            else
            {
                var assemblyNames = GetDefaultAssemblyNames();//?.Select(p => p.Name).ToArray();
                assemblyNames = GetFilterAssemblies(assemblyNames);
                foreach (var name in assemblyNames)
                {
                    try
                    {
                        if (referenceAssemblies.Find(f => f.GetName().Name == name.Name) == null)
                            referenceAssemblies.Add(Assembly.Load(name));
                    }
                    catch (Exception ex)
                    {
                        //ConsoleC.WriteLine(ex, "GetAssemblies(params string[] virtualPaths) error " + name.FullName);
                    }
                }
                _referenceAssembly.AddRange(referenceAssemblies.Except(_referenceAssembly));
            }
            return referenceAssemblies;
        }
        private static IEnumerable<AssemblyName> GetDefaultAssemblyNames()
        {
                var refAssemblyNames = new List<AssemblyName>();
                //----------------------------------------------------------------------------------------------------
                var assemblyFiles = GetAllAssemblyFiles(AppDomain.CurrentDomain.BaseDirectory);
                foreach (var referencedAssemblyFile in assemblyFiles)
                {
                    try
                    {
                        var referencedAssembly = Assembly.LoadFrom(referencedAssemblyFile);
                        if (refAssemblyNames.Find(f => f.Name == referencedAssembly.GetName().Name) == null)
                            refAssemblyNames.Add(referencedAssembly.GetName());
                    }
                    catch (Exception ex)
                    {
                       // ConsoleC.WriteLine(ex, "GetDefaultAssemblyNames() error " + referencedAssemblyFile);
                    }
                }
                //----------------------------------------------------------------------------------------------------
                var assemblies = AppDomain.CurrentDomain.GetAssemblies();
                foreach (var a in assemblies)
                {
                    if (refAssemblyNames.Find(f => f.Name == a.GetName().Name) == null)
                        refAssemblyNames.Add(a.GetName());

                    var aa = a.GetReferencedAssemblies();
                    foreach (var sas in aa)
                    {
                        if (refAssemblyNames.Find(f => f.Name == sas.Name) == null)
                            refAssemblyNames.Add(sas);
                    }
                }

                return refAssemblyNames;
        }
        private static List<Assembly> GetReferenceAssembly(params string[] virtualPaths)
        {
            var refAssemblies = new List<Assembly>();
            var rootPath = AppDomain.CurrentDomain.BaseDirectory;
            var existsPath = virtualPaths.Any();
            //if (existsPath && !string.IsNullOrEmpty(AppConfig.ServerOptions.RootPath))
             //   rootPath = AppConfig.ServerOptions.RootPath;
            var result = _referenceAssembly;
            if (!result.Any() || existsPath)
            {
                var paths = virtualPaths.Select(m => Path.Combine(rootPath, m)).ToList();
                if (!existsPath) paths.Add(rootPath);
                paths.ForEach(path =>
                {
                    var assemblyFiles = GetAllAssemblyFiles(path);

                    foreach (var referencedAssemblyFile in assemblyFiles)
                    {
                        try
                        {
                            var referencedAssembly = Assembly.LoadFrom(referencedAssemblyFile);
                            if (!_referenceAssembly.Contains(referencedAssembly))
                                _referenceAssembly.Add(referencedAssembly);
                            refAssemblies.Add(referencedAssembly);
                        }
                        catch (Exception ex)
                        {
                            //ConsoleC.WriteLine(ex, "GetReferenceAssembly(params string[] virtualPaths) error " + referencedAssemblyFile);
                        }
                    }
                    result = existsPath ? refAssemblies : _referenceAssembly;
                });
            }
            return result;
        }
        private static IEnumerable<AssemblyName> GetFilterAssemblies(IEnumerable<AssemblyName> assemblyNames)
        {
            var notRelatedFile = "";
            var relatedFile = "";
            var pattern = string.Format("^Microsoft.\\w*|^System.\\w*|^DotNetty.\\w*|^runtime.\\w*|^ZooKeeperNetEx\\w*"
                + "|^StackExchange.Redis\\w*|^Consul\\w*|^Newtonsoft.Json.\\w*|^Autofac.\\w*{0}",
               string.IsNullOrEmpty(notRelatedFile) ? "" : $"|{notRelatedFile}");
            Regex notRelatedRegex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
            Regex relatedRegex = new Regex(relatedFile, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
            if (!string.IsNullOrEmpty(relatedFile))
            {
                return
                    assemblyNames.Where(
                        name => !notRelatedRegex.IsMatch(name.Name) && relatedRegex.IsMatch(name.Name)).ToArray();
            }
            else
            {
                return
                    assemblyNames.Where(
                        name => !notRelatedRegex.IsMatch(name.Name)).ToArray();
            }
        }
        private static List<string> GetAllAssemblyFiles(string parentDir)
        {
            var notRelatedFile ="";
            var relatedFile ="";
            var pattern = string.Format("^Microsoft.\\w*|^System.\\w*|^Netty.\\w*|^Autofac.\\w*|^libSkiaSharp.\\w*|^libuv.\\w*|^grpc_csharp_ext.\\w*{0}",
               string.IsNullOrEmpty(notRelatedFile) ? "" : $"|{notRelatedFile}");
            Regex notRelatedRegex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
            Regex relatedRegex = new Regex(relatedFile, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
            if (!string.IsNullOrEmpty(relatedFile))
            {
                return
                    Directory.GetFiles(parentDir, "*.dll").Select(Path.GetFullPath).Where(
                        a => !notRelatedRegex.IsMatch(Path.GetFileName(a)) && relatedRegex.IsMatch(Path.GetFileName(a))).ToList();
            }
            else
            {
                return
                    Directory.GetFiles(parentDir, "*.dll").Select(Path.GetFullPath).Where(
                        a => !notRelatedRegex.IsMatch(Path.GetFileName(a))).ToList();
            }
        }

        #region AssemblyLoad
        private static Dictionary<string, Assembly> m_dicAAssemblys = new Dictionary<string, Assembly>();
        /// <summary>
        /// 通过给定程序集的长格式名称加载程序集
        /// </summary>
        /// <param name="assemblyString">程序集名称的长格式</param>
        /// <param name="filePath">程序集所在目录，默认空，为当前目录</param>
        /// <returns></returns>
        public static Assembly AssemblyLoad(string assemblyString, string filePath = "")
        {
            try
            {
                Assembly assembly = null;
                Type type = null;

                if (m_dicAAssemblys.ContainsKey(assemblyString))
                    return m_dicAAssemblys[assemblyString];
                //------------------------------------------------------------------
                try
                {
                    type = Type.GetType(assemblyString);
                }
                catch { }
                try
                {
                    if (type != null)
                        assembly = Assembly.GetAssembly(type);
                }
                catch { }
                if (assembly != null)
                {
                    m_dicAAssemblys[assemblyString] = assembly;
                    return assembly;
                }
                //------------------------------------------------------------------
                try
                {
                    assembly = Assembly.Load(assemblyString);
                    if (assembly != null)
                    {
                        m_dicAAssemblys[assemblyString] = assembly;
                        return assembly;
                    }
                }
                catch { }

                if (string.IsNullOrEmpty(filePath))
                    filePath = AppDomain.CurrentDomain.BaseDirectory;

                if (!string.IsNullOrEmpty(filePath) && !string.IsNullOrEmpty(assemblyString))
                {
                    var file = filePath;
                    if (!System.IO.File.Exists(file))
                    {
                        var astr = assemblyString;
                        file = System.IO.Path.Combine(filePath, astr + ".dll");
                        while (true)
                        {
                            if (System.IO.File.Exists(file))
                                break;
                            if (astr.IndexOf('.') < 0)
                                return null;
                            astr = astr.Substring(0, astr.LastIndexOf('.'));
                            file = System.IO.Path.Combine(filePath, astr + ".dll");
                        }
                    }
                    //-------------------------------------------------------------------
                    if (System.IO.File.Exists(file))
                    {
                        if (m_dicAAssemblys.ContainsKey(file))
                            return m_dicAAssemblys[file];
                        //LoggingHelper.Debug("..1111." + file);
                        m_dicAAssemblys[file] = Assembly.LoadFrom(file);
                        return m_dicAAssemblys[file];
                        //using (System.IO.FileStream FS = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                        //{
                        //    byte[] FileByteArray = new byte[(int)FS.Length];
                        //    FS.Read(FileByteArray, 0, FileByteArray.Length);

                        //    m_dicAAssemblys[file] = Assembly.Load(FileByteArray);
                        //    return m_dicAAssemblys[file];
                        //}
                    }
                    else
                    {
                        LogUtil.Error("filePath=" + filePath + ",assemblyString=" + assemblyString + ",不存在@AssemblyLoad"); return null;
                    }
                }
                else
                {
                    LogUtil.Error("filePath或assemblyString不能这空@AssemblyLoad");
                }
            }
            catch (Exception ex)
            {
                LogUtil.Error(ex.Message + "@AssemblyLoad");
            }
            return null;
        }
        #endregion

    }
}